package com.smzd.ccms.core;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

import com.smzd.ccms.dao.ChannelConfigDao;
import com.smzd.ccms.dao.ChannelDao;
import com.smzd.ccms.dao.CompletConfigDao;
import com.smzd.ccms.dao.CompletDao;
import com.smzd.ccms.dao.CompletParamConfigDao;
import com.smzd.ccms.dao.CompletParamDao;
import com.smzd.ccms.dao.SiteDao;
import com.smzd.ccms.domain.Channel;
import com.smzd.ccms.domain.ChannelConfig;
import com.smzd.ccms.domain.Complet;
import com.smzd.ccms.domain.CompletConfig;
import com.smzd.ccms.domain.CompletParam;
import com.smzd.ccms.domain.CompletParamConfig;
import com.smzd.ccms.domain.Site;
import com.smzd.ccms.domain.SysDict;

/**
 * 管理所有的小组件和小组件的配置。其中小组件的配置是核心，小组件是固定的，两者要关联起来。<br>
 * 小组件的配置的概念是这样的：<br>
 * 一个模板被配置的时候，必须为模板的每一个小组件占位配置一个小组件，并为这个小组件配置若干的参数以供运行。<br
 * 因此小组件的配置由一个组合key所唯一确定：模板的配置+小组件占位（spotid）。之所以是模板的配置而不是模板，是因为模板可能被复用。<br>
 * 模板的配置在CCMS中被封装为一个ViewMapper对象，而该对象唯一对应一个请求模式（Pattern）。<br>
 * ViewMapper被放在内存当中作为高速缓存使用，而小组件的配置也会同时放在ViewMapper中高速缓存。因此这个组件就变得不是很重要。<br>
 * 另外，加载ViewMapper到内存中的组件，也当完成小组件的配置工作。这个组件的功能就更加退化了，除非这个类就是加载ViewMapper到内存中的组件。<br>
 * <br>
 * <b>注：该类作为装配ViewMapper的启动组件。</b>
 * 
 * @author YuZhongda
 * 
 */
@Component
public class CompletPluginManager implements InitializingBean, ApplicationEventPublisherAware {
	private static Map<String, Complet> completCache = new HashMap<String, Complet>();
	private static Map<String, Site> siteCache = new HashMap<String, Site>();
	private static CompletPluginManager instance;

	public static CompletPluginManager getInstance() {
		return instance;
	}
	ApplicationEventPublisher eventPublisher;

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
		this.eventPublisher = eventPublisher;
	}

	@Autowired
	private SiteDao siteDao;
	@Autowired
	private ChannelDao channelDao;
	@Autowired
	private ChannelConfigDao channelConfigDao;
	@Autowired
	private CompletConfigDao completConfigDao;
	@Autowired
	private CompletParamDao completParamDao;
	@Autowired
	private CompletParamConfigDao completParamConfigDao;
	@Autowired
	private CompletDao completDao;

	private CompletPluginManager() {
		CompletPluginManager.instance = this;
	}

	public static Complet getComplet(String className) {
		return completCache.get(className);
	}

	public static Site getSite(String site) {
		return siteCache.get(site);
	}

	public void loadCcmsConfigs() {
		// 加载所有的栏目配置，并组装ViewMapper。
		List<ChannelConfig> channelConfigs = loadChannelConfigs();
		// 准备小组件的配置数据，并将其分类挂载到栏目配置里面去。
		List<CompletConfig> completConfigs = loadCompletConfigs();
		// 将小组件的配置安装到栏目配置里面去
		initChannelCompletConfigs(channelConfigs, completConfigs);
		// 加载小组件。小组件的加载其实就是new出所配置的类的实例来，然后根据其ID注册到plugins列表里面就行了。到时候会从这个列表中查询的。
		List<Complet> complets = loadCompletPlugins();
		initCompletConfigs(completConfigs, complets);
		
		eventPublisher.publishEvent(new SitesLoadedEvent(""));
	}

	private void initCompletConfigs(List<CompletConfig> completConfigs,
			List<Complet> complets) {
		for(CompletConfig cc : completConfigs){
			for(Complet c : complets){
				if(cc.getComplet().getId()-c.getId()==0){
					cc.setComplet(c);
					break;
				}
			}
		}
	}

	private void initChannelCompletConfigs(List<ChannelConfig> channelConfigs,
			List<CompletConfig> completConfigs) {
		for(ChannelConfig cc : channelConfigs){
			for(CompletConfig c : completConfigs){
				if(c.getScope().getId()-cc.getId()==0){
					cc.putCompletConfig(c);
					c.setScope(cc);
				}
			}
		}
	}

	// 装载所有的小组件处理器，并将其加入高速缓存。
	private List<Complet> loadCompletPlugins() {
		List<CompletParam> completParams = completParamDao
				.getAllCompletParams();
		List<Complet> complets = completDao.getAllComplets();
		for (Complet c : complets) {
			// 循环检查小组件默认参数配置
			for (CompletParam cp : completParams) {
				// 如果这个参数配置是当前小组件配置的默认参数，则把该参数put到当前小组件配置里面
				if (c.getId() - cp.getScope().getId() == 0) {
					c.putParam(cp);
				}
			}
			String className = c.getClassName();
			if (!StringUtils.isEmpty(className)) {
				try {
					@SuppressWarnings("unchecked")
					Class<CompletPlugin> clazz = (Class<CompletPlugin>) Class
							.forName(className);
					Constructor<CompletPlugin> constructor = clazz
							.getConstructor(int.class);
					CompletPlugin plugin = constructor.newInstance(c.getId());
					c.setExecuter(plugin);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			completCache.put(className, c);
		}
		return complets;
	}

	private List<CompletConfig> loadCompletConfigs() {
		List<CompletConfig> completConfigs = completConfigDao
				.getAllCompletConfigs();
		List<CompletParamConfig> completParamConfigs = completParamConfigDao
				.getAllCompletParamConfigs();
		// 先完成小组件参数配置情况的加载
		for (CompletConfig cc : completConfigs) {

			// 循环检查小组件独特参数配置
			for (CompletParamConfig cpc : completParamConfigs) {
				// 如果这个小组件参数配置是针对当前小组件配置的单独配置，则put进去，这样会覆盖默认参数配置
				if (cpc.getScope().getId() - cc.getId() == 0) {
					cc.putCpConfig(cpc);
				}
			}
		}
		return completConfigs;
	}

	/**
	 * 加载所有的栏目视角配置ChannelConfig。<br>
	 * 每一个ChannelConfig都对应一个SysDict字典表，以表明该配置是基于哪个系统词典的配置。
	 * 
	 * @return
	 */
	private List<ChannelConfig> loadChannelConfigs() {
		// 获取所有的栏目视角配置，待组装。
		List<ChannelConfig> channelConfigs = channelConfigDao
				.getAllChannelConfigs();
		// 获取所有的栏目，等待汇集栏目视角配置
		List<Channel> channels = channelDao.getAllChannels();
		// 获取所有的站点，等待汇集栏目
		List<Site> sites = siteDao.getAllSites();
		// 先把站点都添加到缓存里面去
		for (Site site : sites) {
			siteCache.put(site.getPath(), site);
		}
		// 给每个channel绑定site，同时把channel汇集组装到site里面去。双向绑定
		for (Channel ch : channels) {
			for (Site s : sites) {
				if (ch.getOwner().getId() - s.getOwner().getId() == 0) {
					ch.setSite(s);
					s.addChannel(ch);
					break;
				}
			}
		}
		// 获取或创建ViewMapper缓存实例
		ViewMapperCache cache = ViewMapperCache.getInstance();
		// 对每个栏目视角创建ViewMapper。这里注意，新添加的后台视角无需添加ViewMapper。
		// 下面的循环同时完成了两件事：
		// 1. 对栏目视角创建ViewMapper并加入缓存；
		// 2. 把栏目和栏目视角做双向绑定
		for (ChannelConfig cfg : channelConfigs) {
			for (Channel ch : channels) {
				if (cfg.getScope().getId() - ch.getId() == 0) {
					cfg.setChannel(ch);// 栏目视角和栏目的关系绑定
					if(cfg.getSysDict().getId()<3){
						String prefix = getPrefix(cfg.getSysDict());
						String site = ch.getSite().getPath();
						String channel = ch.getPath();
						String page = cfg.getSysDict().stringValue();// cfg.getPath();
						String view = cfg.getTemplate().getPath();
						ViewMapper vm = new ViewMapper(prefix, site, channel, page,
								view);
						cache.addViewMapper(vm);
						cfg.setViewMapper(vm);
						vm.getEntityWrapper().setChannelConfig(cfg);
						vm.getEntityWrapper().setChannel(ch);
						vm.getEntityWrapper().setSite(ch.getSite());
					}else{
						ch.addChannelCfg(cfg);
					}
					break;
				}
			}
		}
		return channelConfigs;
	}

	private String getPrefix(SysDict sysDict) {
		if (sysDict.getId() == 1 || sysDict.getId() == 2) {
			return "p";
		}
		return "admin";
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		loadCcmsConfigs();
	}
}
